iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 14
1
Mobile Development

程式初學:Android與Kotlin系列 第 14

Day 14--Intent Activity,Back Stack

  • 分享至 

  • xImage
  •  

Intent Activity

利用SharedPreferences儲存資料,練習做一個類似密碼鎖的頁面

除了MainActivity外,再建立一個Password Activity的頁面
當設定密碼後,會顯示Password的頁面
須輸入正確密碼才會回到MainActivity

在MainActivity中建立一個Intent物件,用於啓動新的頁面

Intent(參數1,參數2):

  • 參數1 從這個Context
  • 參數2 到要啓動的Activity

val intentPass = Intent(this, PassActivity::class.java)

再呼叫startActivity並把Intent物件傳入
當按下SAVE按鍵時就是設定密碼,跳轉到須要輸入密碼的頁面

btn_save.setOnClickListener {
            ...
                startActivity(intentPass)   //  intent to PassActivity
}

當輸入的密碼正確,呼叫finish()
將目前的密碼Activity結束掉,再顯示回原本的MainActivity頁面

程式碼如下

@ExperimentalStdlibApi
class MainActivity : AppCompatActivity() {

    companion object {
        var password = ""
        var isResume = false
        lateinit var intentPass: Intent
    }


    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val pref = SharedPreferences(this)

        intentPass =
            Intent(this, PassActivity::class.java) //.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)

        if (!pref.getData().isNullOrEmpty() && !isResume) {
            startActivity(intentPass)   //  intent to PassActivity
            password = pref.getData().toString()
        }

        tv_data.text = pref.getData()

        btn_save.setOnClickListener {
            if (!ed_data.text.isNullOrEmpty()) {
                pref.saveData(ed_data.text.toString())
            }
            tv_data.text = pref.getData()
            password = pref.getData().toString()

            if (password != "" && password != "null") {
                startActivity(intentPass)   //  intent to PassActivity
            }
        }

        btn_delete.setOnClickListener {
            pref.delete()
            ed_data.text.clear()
            tv_data.text = pref.getData()
            password = ""
        }
    }

    override fun onResume() {
        super.onResume()

        if (isResume && password != "" && password != "null") {
            startActivity(intentPass)   //  intent to PassActivity
        }
    }

    override fun onPause() {
        super.onPause()
        isResume = true
    }
}
@ExperimentalStdlibApi
class PassActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_pass)

        val passwordList = mutableListOf<Int>()

        tv_hint.text = MainActivity.password

        btn1.setOnClickListener(::setPwdAndCheck)
        btn2.setOnClickListener(::setPwdAndCheck)
        btn3.setOnClickListener(::setPwdAndCheck)
        btn4.setOnClickListener(::setPwdAndCheck)
        btn5.setOnClickListener(::setPwdAndCheck)
        btn6.setOnClickListener(::setPwdAndCheck)
        btn7.setOnClickListener(::setPwdAndCheck)
        btn8.setOnClickListener(::setPwdAndCheck)
        btn9.setOnClickListener(::setPwdAndCheck)
        btn0.setOnClickListener(::setPwdAndCheck)

        btn_cancel.setOnClickListener {
            if (passwordList.isNotEmpty()) passwordList.removeLast()
            printOOOO(passwordList.size)
            if (passwordList.size == 0) tv_password.text = "_ _ _ _"
        }
    }

    override fun onBackPressed() {
        super.onBackPressed()
        finishAffinity()
    }
    
    private fun setPwdAndCheck(view: View) {
        val num = (view as TextView).text.toString().toIntOrNull()?:return
        if (passwordList.size < 4) passwordList.add(num)
        printOOOO(passwordList.size)
        finish(passwordList)
    }

    private fun printOOOO(int: Int) {
        tv_password.text = ""
        var string = ""
        for (i in 1..int) {
            tv_password.text = tv_password.text.toString() + "● "
            string = ""
            for (j in 1..(4 - int)) {
                string += "_  "
            }
        }
        tv_password.text = tv_password.text.toString() + string
    }

    private fun finish(list: MutableList<Int>) {
        var string = ""
        list.forEach {
            string += it
        }
        if (string == MainActivity.password) {
            this.finish()
            MainActivity.isResume = false
        }
    }
}

Back Stack

要注意的是,當activity切換時,未顯示的畫面會被加到back stack
預設是若按系統的返回鍵,就會丟掉目前畫面,再把back stack最上層的畫面顯示出來
等於按返回鍵可以跳過不用輸入密碼,就回到Main頁面

所以在PassActivity要覆寫按返回鍵的動作,結束所有的activity

    override fun onBackPressed() {
        super.onBackPressed()

        finishAffinity()
    }

這樣在密碼頁面按下返回鍵就會退出app,而不會回到Main頁面

還有因爲code寫的實在太糙了,想要在app從背景回到前景時
偵測有設定密碼的話,也startActivity到密碼頁面
所以在onCreate(),onResume(),都加入startActivity
結果在造成實例多個密碼頁面,想要用SINGLE_TOP

Intent(this, PassActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)

限制只生成一個實例來解決,不過還是有問題
查到官方有一段說明,可能是這個原因

假設任務的返回堆棧包含根Activity A以及Activity B、C和位於頂部的D(堆棧為ABCD;D位於頂部)。收到以D類型Activity為目標的intent。如果D採用默認的"standard"啟動模式,則會啟動該類的新實例,並且堆棧將變為ABCDD。但是,如果D的啟動模式為"singleTop",則D的現有實例會通過onNewIntent()接收intent,因為它位於堆棧頂部,堆棧仍為ABCD。但是,如果收到以B類型Activity為目標的intent,則會在堆棧中添加B的新實例,即使其啟動模式為"singleTop"也是如此。

嘗試改用FLAG_ACTIVITY_CLEAR_TOP就可以了

如果要啟動的Activity已經在當前任務中運行,則不會啟動該Activity的新實例,而是會銷毀位於它之上的所有其他Activity,並通過onNewIntent()將此intent傳送給它的已恢復實例(現在位於堆棧頂部)。

參考
https://developer.android.com/training/basics/firstapp/starting-activity#BuildIntent

https://developer.android.com/guide/components/activities/tasks-and-back-stack

https://developer.android.com/reference/android/app/Activity#finishAffinity()


上一篇
Day 13--SharedPreferences 簡單資料儲存
下一篇
Day 15--天氣app(一) Open Data API,JSON
系列文
程式初學:Android與Kotlin30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言